/*
 *   Calculator Frame
 *   Copyright (C) 2012 Joe Huang
 *   
 * 	 This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * 2012/10/28 V0.2
 * Using abstract class CalButton as an interface to other classes, along with inheritance to create concrete classes
 * Related OO basic skills: Inheritance, Polymorphism
 * Design Principle: Program to interface, not to implementation(Concrete class)
 * The CalButtonListener depends only on the abstract CalButton class
 * 
 * 2012/11/03 V0.21
 * 1. Let all the buttons share the same concrete CalButtonListener instead of making a new one foe each button.
 * 2. Fix bug for backspace button
 * 
 * 2012/11/03 V0.3
 * 1. Adopt the command pattern(Or strategy? not sure) to decouple the buttons from their behaviors.
 *    For now, the CalCommand objects still need the name of buttons to do calculations, I mean, they are not completely decoupled yet.
 * 2. Change the naming style of variables to camel style like abcDEF
 * 
 * 2012/11/04 V0.31
 * 1. Modify class CalCommand to decouple the (GUI View)buttons completely from their behaviors(Commands).
 *    Change void execute(JButton btn) to void execute(String calName) , calName is required for commands like operands/operators
 *    Although some commands such as clear or backspace do NOT need calName, they still share the same execute(String calName) for transparency
 * 2. Ready for the commands to move out of the frame in the next version.
 * 
 * 2012/11/04 V0.4
 * Move the commands to package calculator.model
 * Issue: The CalCulatorFrame still depends on not only the CalButtonListner but also concrete commands, how do I decouple them?
 * 
 * 2012/11/09 V0.5
 * Modify the combination of buttons
 * 
 * 2012/11/10 V0.6
 * Apply Observer Pattern for ui/model interaction
 * 
 */
package calculator.ui;

import calculator.model.*;

import java.awt.Container;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.Observable;
import java.util.Observer;

import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.WindowConstants;

// Define the buttons of this calculator
class CalButtonTable {
	
	public static final int NullButton = -1;     // buttons that do nothing
	public static final int OperandButton = 0;   // operands
	//public static final int BracketButton = 1;   // brackets
	public static final int OperatorButton = 2;  // operators +,-,*,/,^,...
	//public static final int FunctionButton = 3;  // sin, cos, sqrt,...
	public static final int ClearButton = 4;     // clear the inputs and answers
	public static final int BackspaceButton = 5; // backspace button
	public static final int DoCalculationButton = 6; // do the calculation
	
	
	public static int length() { return buttons.length; }
	public static String nameAt(int index) { return buttons[index]; }
	public static int typeAt(int index) { return btn_types[index]; }
	
	private final static String buttons[] = {"(", ")", "C", "Backspace", "NULL", 
											 "7", "8", "9", "/", "^", 
											 "4", "5", "6", "*", "%", 
											 "1", "2", "3", "-", "=", 
											 "0", "NULL", ".", "+"};
	private final static int btn_types[] = { 2, 2, 4, 5, -1,
											 0,  0, 0, 2, 2,
											 0,  0, 0, 2, 2,
											 0,  0, 0, 2, 6,
											 0, -1, 0, 2 };
}

class CalButtonListener extends MouseAdapter {
	CalButtonListener(CalculationModel calModel, JTextField tfInputs, int btnType){
		this.calModel = calModel;
		this.tfInputs = tfInputs;
		this.btnType = btnType;
	}
	public void mouseClicked(MouseEvent e) {
		
		String btnName = e.getComponent().getName();
		String newInputs = tfInputs.getText();
		
		switch(this.btnType) {
		case(CalButtonTable.OperandButton):
			calModel.doOperand(btnName, newInputs);
			calModel.notifyObservers();
			break;
		case(CalButtonTable.OperatorButton):
			calModel.doOperator(btnName, newInputs);
			calModel.notifyObservers();
			break;
		case(CalButtonTable.ClearButton):
			calModel.doClear();
			calModel.notifyObservers();
			break;
		case(CalButtonTable.BackspaceButton):
			calModel.doBackSpace(newInputs);
			calModel.notifyObservers();
			break;
		case(CalButtonTable.DoCalculationButton):
			calModel.doCalculation(newInputs);
			calModel.notifyObservers();
			break;
		default:
			break;
		
		}
		
		calModel.notifyObservers();
	}
	
	private CalculationModel calModel;
	private JTextField tfInputs;
	private int btnType;
}

class InputsField extends JTextField implements Observer {
	
	@Override
	public void update(Observable obs, Object obj) {
		if (obs instanceof CalculationModel) {
			CalculationModel calModel = (CalculationModel)obs;
			this.setText(calModel.getInputs());
		}
	}
	
}

class AnswerField extends JLabel implements Observer {
	@Override
	public void update(Observable obs, Object obj) {
		if (obs instanceof CalculationModel) {
			CalculationModel calModel = (CalculationModel)obs;
			if (calModel.getAnswer()!=null) this.setText(calModel.getAnswer());
		}
	}
}

public class CalculatorFrame extends JFrame {

	public CalculatorFrame() {
		addComponentsToPane();
		this.pack();
		this.setTitle("A Simple Calculator");
		this.setVisible(true);
		this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
	}
	
	private void addComponentsToPane() {
		
		// panels
		Container mainpanel = this.getContentPane();
		JPanel buttonpanel = new JPanel();
		
		// Layout
		mainpanel.setLayout(new BoxLayout(mainpanel, BoxLayout.Y_AXIS));
		buttonpanel.setLayout(new GridBagLayout());
		final int dim_x=5;
		GridBagConstraints c = new GridBagConstraints();
		c.fill = GridBagConstraints.HORIZONTAL;
		
		// add inputs/answer field
		InputsField tfInputs = new InputsField();
		AnswerField labelAnswer = new AnswerField();
		labelAnswer.setText(" ");
		
		CalculationModel calModel = new SimpleCalculationModel();
		calModel.addObserver(tfInputs);
		calModel.addObserver(labelAnswer);
		
		// Listeners
		CalButtonListener operandListener = new CalButtonListener(calModel, tfInputs, CalButtonTable.OperandButton);
		CalButtonListener operatorListener = new CalButtonListener(calModel, tfInputs, CalButtonTable.OperatorButton);
		CalButtonListener clearListener = new CalButtonListener(calModel, tfInputs, CalButtonTable.ClearButton);
		CalButtonListener backSpaceListener = new CalButtonListener(calModel, tfInputs, CalButtonTable.BackspaceButton);
		CalButtonListener doCalculationListener = new CalButtonListener(calModel, tfInputs, CalButtonTable.DoCalculationButton);
		
		for (int i=0; i<CalButtonTable.length(); i++) {
			JButton num = new JButton(CalButtonTable.nameAt(i));
			num.setName(CalButtonTable.nameAt(i));
			
			switch(CalButtonTable.typeAt(i)) {
			case CalButtonTable.OperandButton:
				num.addMouseListener(operandListener);
				break;
			case CalButtonTable.OperatorButton:
				num.addMouseListener(operatorListener);
				break;
			case CalButtonTable.ClearButton:
				num.addMouseListener(clearListener);
				break;
			case CalButtonTable.BackspaceButton:
				num.addMouseListener(backSpaceListener);
				break;
			case CalButtonTable.DoCalculationButton:
				num.addMouseListener(doCalculationListener);
				break;
			default:
				break;
			}
			
			// GridBagLayout for buttons
			c.gridx = i%dim_x;
			c.gridy = i/dim_x;
			if (CalButtonTable.nameAt(i).equals("0") || CalButtonTable.nameAt(i).equals("Backspace")) c.gridwidth = 2;
			else c.gridwidth = 1;
			if (CalButtonTable.nameAt(i).equals("=")) {c.gridheight = 2; c.fill = GridBagConstraints.BOTH;}
			else c.gridheight = 1;
			buttonpanel.add(num, c);
		}
		
		mainpanel.add(tfInputs);
		mainpanel.add(labelAnswer);
		mainpanel.add(buttonpanel);
	}

}
